// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Util from './Util';
import CameraPlayer from './CameraPlayer';
import MicrophonePlayer from './MicrophonePlayer';
import {
    PACKAGE_HEADER_LENGTH,
    MOBILE_CAMERA_MODE_MAP,
    MEDIA_MSG_HEADER_COUNT,
    CAMERA_RSP_LEN,
    MICROPHONE_RSP_LEN,
    MIC_START_RECORD_RSP_LEN,
    PC_CAMERA_MAX_HEIGHT
} from './config/commonConfig';
import PROTOCOL_CONFIG from './config/protocolConfig';

class DeviceHardwareHandler {
    constructor(options) {
        this.options = {...options};
        this.sendMediaData = this.options.sendMediaData;
        this.setMsgHeader = this.options.setMsgHeader;
        this.send = this.options.send;
        this.util = new Util();
        this.sensorMsgType = PROTOCOL_CONFIG.SENSOR_MESSAGE_TYPE;
        this.gpsLocationMsgType = PROTOCOL_CONFIG.GPS_LOCATION_MESSAGE_TYPE;
        this.defaultCameraId = "";
      }

    handleStartPreviewReq(cameraMsgBody, cameraMsgHeader) {
        if (this.cameraPlayer) {
            return;
        }
        this.cameraMsgBody = cameraMsgBody;
        this.cameraMsgHeader = cameraMsgHeader;
        const originWidth= this.cameraMsgBody.width;
        const originHeight = this.cameraMsgBody.height;
        let videoOptions = {
            width: { ideal: originWidth},
            height: { ideal: originHeight},
            frameRate: { ideal: this.cameraMsgBody.fps}
        };
        if (originWidth > PC_CAMERA_MAX_HEIGHT && !this.options.isMobile) {
            let cameraWidth = (originHeight * PC_CAMERA_MAX_HEIGHT) / originWidth;
            cameraWidth = this.util.changeToMultipleOfEight(cameraWidth);
            videoOptions.width.ideal = cameraWidth;
            videoOptions.height.ideal = PC_CAMERA_MAX_HEIGHT;
        }
        if (this.defaultCameraId) {
          videoOptions.deviceId = {
            exact: this.defaultCameraId
          }
        } else if (this.options.isMobile){
          videoOptions.facingMode = {
              exact: MOBILE_CAMERA_MODE_MAP[cameraMsgHeader.devId]
          };
        }
        this.cameraPlayer = new CameraPlayer({
            videoOptions,
            encodeWidth: this.cameraMsgBody.width,
            encodeHeight: this.cameraMsgBody.height,
            sendOutBufHandler: this.sendMediaData,
            x264WasmPath: this.options.libPath,
            devId: cameraMsgHeader.devId
        });
    }

    setDefaultCamera(devId) {
      this.defaultCameraId = devId;
    }

    handleStopPreviewReq(cameraMsgHeader) {
        this.cameraMsgHeader = cameraMsgHeader;
        this.sendMediaMsgCmdData(CAMERA_RSP_LEN, this.options.cameraMsgType.OPT_CAMERA_STOP_PREVIEW_RSP, 'CAMERA');
        if (this.cameraPlayer) {
            this.cameraPlayer.stopCameraVideo();
            this.cameraPlayer = null;
        }
    }

    handleSetRecordParam(microPhoneMsgHeader) {
        this.microPhoneMsgHeader = microPhoneMsgHeader;
        this.sendMediaMsgCmdData(MICROPHONE_RSP_LEN, this.options.microPhoneMsgType.OPT_MIC_SET_PARAM_RSP, 'MICROPHONE');
    }

    handleStartRecordReq(microPhoneMsgBody) {
        if (this.microphonePlayer) {
            return;
        }
        this.microPhoneMsgBody = microPhoneMsgBody;
        this.sendMediaMsgCmdData(MIC_START_RECORD_RSP_LEN, this.options.microPhoneMsgType.OPT_MIC_START_RECORD_RSP, 'MICROPHONE');
        this.microphonePlayer = new MicrophonePlayer({
            audioOptions: {
                sampleRate: 48000,
                channelCount: 1,
                maxFrameSize: this.microPhoneMsgBody?.maxFrameSize || 7680
            },
            sendOutBufHandler: this.sendMediaData,
            microPhoneOutputType: this.options.microPhoneOutputType
        });
    }

    handleStopRecordReq() {
        this.sendMediaMsgCmdData(MICROPHONE_RSP_LEN, this.options.microPhoneMsgType.OPT_MIC_STOP_RECOED_RSP, 'MICROPHONE');
        if (this.microphonePlayer) {
            this.microphonePlayer.stopAudio();
            this.microphonePlayer = null;
        }
    }

    handleSensorEnable(devId) {
        this.sendSensorMsgData(this.sensorMsgType.OPT_SENDOR_ENABLE_REQ, devId);
    }

    handleSensorDisable(devId) {
        if (window.DeviceMotionEvent) {
            this.util.unbind(window, 'devicemotion');
        }
        if (window.DeviceOrientationEvent) {
            this.util.unbind(window, 'deviceorientation');
        }
        this.sendSensorMsgData(this.sensorMsgType.OPT_SENSOR_DISABLE_REQ, devId);
    }

    pauseDevice() {
        if (this.cameraPlayer) {
            this.cameraPlayer.stopCameraVideo();
            this.cameraPlayer = null;
        }

        if (this.microphonePlayer) {
            this.microphonePlayer.stopAudio();
            this.microphonePlayer = null;
        }

        if (window.DeviceMotionEvent) {
            this.util.unbind(window, 'devicemotion');
        }
        if (window.DeviceOrientationEvent) {
            this.util.unbind(window, 'deviceorientation');
        }
    }

    resumeDevice() {
        if (this.cameraPlayer) {
            this.handleStartPreviewReq(this.cameraMsgBody, this.cameraMsgHeader);
        }

        if (this.microphonePlayer) {
            this.handleStartRecordReq(this.microPhoneMsgBody);
        }

        if (window.DeviceMotionEvent) {
            this.util.bind(window, 'devicemotion', this.getDeviceMotion.bind(this));
        }
        if (window.DeviceOrientationEvent) {
            this.util.bind(window, 'deviceorientation', this.getDeviceOrientation.bind(this));
        }
    }

    getDeviceMotion(event) {
        const accuracy = 3;
        // 加速度传感器 TYPE_ACCELEROMETER
        this.sensorDevId = 0;
        const acceleration = event.accelerationIncludingGravity;
        const x = acceleration.x || 0.111;
        const y = acceleration.y || 0.111;
        const z = acceleration.z || 0.111;

        if (x && y && z) {
            const body = x.toString() + ':' + y.toString() + ':' + z.toString() + ':' + accuracy.toString() + ':';
            const strEncoder = new TextEncoder();
            const data = strEncoder.encode(body);
            this.sendMediaData('SENSOR', this.sensorMsgType.OPT_SENSOR_DATA, data, this.sensorMsgType.TYPE_ACCELEROMETER);
        }
    }

    getDeviceOrientation(event) {
        // 陀螺仪传感器 TYPE_GYROSCOPE
        const accuracy = 3;
        this.sensorDevId = 1;
        const x = event.beta || 0.123;
        const y = event.gamma || 0.123;
        const z = event.alpha || 0.123;

        if (x && y && z) {
            const body = x.toString() + ':' + y.toString() + ':' + z.toString() + ':' + accuracy.toString() + ':';
            const strEncoder = new TextEncoder();
            const data = strEncoder.encode(body);
            this.sendMediaData('SENSOR', this.sensorMsgType.OPT_SENSOR_DATA, data, this.sensorMsgType.TYPE_GYROSCOPE);
        }
    }

    getUserLocation() {
        if("geolocation" in navigator){
            const option = {   
                enableHighAccuracy : true,
                timeout : Infinity,   
                maximumAge : 0   
            };
            // 监听获取实时位置信息
            this.watchID = navigator.geolocation.watchPosition(this.userLocationSuccess.bind(this), this.userLocationError.bind(this), option);
        } else {
            console.log("Your browser does not support location.");
        }
    }

    userLocationSuccess(position) {
        const coords=position.coords;
        const longitude = coords.longitude; // 经度
        const latitude = coords.latitude; // 纬度
        const altitude = coords.altitude; // 海拔
        const speed = coords.speed; // 每秒速度
        const bearing = coords.heading; // 以360度表示的方向

        let body = '';
        if (longitude) {
            body+=`longitude=${longitude}:`;
        }
        if (latitude) {
            body+=`latitude=${latitude}:`;
        }
        if (altitude) {
            body+=`altitude=${altitude}:`;
        }
        if (speed) {
            body+=`speed=${speed}:`;
        }
        if (bearing) {
            body+=`bearing=${bearing}`;
        }

        if (body.charAt(body.length - 1) === ':') {
            body = body.substring(0, body.length - 1);
        }

        const strEncoder = new TextEncoder();
        const data = strEncoder.encode(body);
        this.sendMediaData('GpsLocation', this.gpsLocationMsgType.OPT_LOCATION_DATA, data);
    }

    userLocationError(error) {
        switch(error.code){
            case error.TIMEOUT:
                console.log("TIMEOUT");
                break;  
            case error.PERMISSION_DENIED:
                console.log("PERMISSION_DENIED");
                break;  
            case error.POSITION_UNAVAILABLE:
                console.log("POSITION_UNAVAILABLE");
                break;
            default:
                break;
        }
    }

    stopLocationWatch() {
        // 停止监听位置信息
        navigator.geolocation.clearWatch(this.watchID);
    }

    sendSensorMsgData(req, devId) {
        let rspBuf = new Uint8Array(MEDIA_MSG_HEADER_COUNT);
        this.makeSensorHeaderBuf(rspBuf, req, rspBuf.length, devId);
        let msgBodyLen = rspBuf.byteLength;
        let typedBuf = new Uint8Array(PACKAGE_HEADER_LENGTH + msgBodyLen);
        typedBuf.set(new Uint8Array(rspBuf), PACKAGE_HEADER_LENGTH);
        this.setMsgHeader(typedBuf, 'SENSOR', msgBodyLen);
        this.send(typedBuf.buffer);
    }

    sendMediaMsgCmdData(bufLen, msgType, mediaType) {
        let rspBuf = new Uint8Array(bufLen);
        this.makeCommonHeaderBuf(rspBuf, msgType, rspBuf.length, mediaType);
        // PCM 0 0 ; OPUS 0 1
        if (msgType === this.options.microPhoneMsgType.OPT_MIC_START_RECORD_RSP && this.options.microPhoneOutputType === 'OPUS') {
            rspBuf[rspBuf.length - 2] = 1 >> 8;
            rspBuf[rspBuf.length - 1] = 1 & 0xFF;
        }
        let msgBodyLen = rspBuf.byteLength;
        let typedBuf = new Uint8Array(PACKAGE_HEADER_LENGTH + msgBodyLen);
        typedBuf.set(new Uint8Array(rspBuf), PACKAGE_HEADER_LENGTH);
        this.setMsgHeader(typedBuf, mediaType, msgBodyLen);
        this.send(typedBuf.buffer);
    }

    makeSensorHeaderBuf(headerBuf, rspValue, dataLen, devId) {
        headerBuf[0] = 1 >> 8;
        headerBuf[1] = 1 & 0xFF;
        headerBuf[2] = rspValue >> 8;
        headerBuf[3] = rspValue & 0x00FF;
        headerBuf[4] = 0 >> 8;
        headerBuf[5] = 0 & 0xFF;
        headerBuf[6] = devId >> 8;
        headerBuf[7] = devId & 0xFF;
        headerBuf[8] = (dataLen & 0xFF000000) >> 24;
        headerBuf[9] = (dataLen & 0x00FF0000) >> 16;
        headerBuf[10] = (dataLen & 0x0000FF00) >> 8;
        headerBuf[11] = dataLen & 0x000000FF;
        headerBuf[12] = 0 >> 8;
        headerBuf[13] = 0 & 0xFF;
        headerBuf[14] = 0 >> 8;
        headerBuf[15] = 0 & 0xFF;
    }

    makeLocationHeaderBuf(headerBuf, rspValue, dataLen) {
        headerBuf[0] = 1 >> 8;
        headerBuf[1] = 1 & 0xFF;
        headerBuf[2] = rspValue >> 8;
        headerBuf[3] = rspValue & 0x00FF;
        headerBuf[4] = 4 >> 8;
        headerBuf[5] = 4 & 0x00FF;
        headerBuf[6] = 0 >> 8;
        headerBuf[7] = 0 & 0xFF;
        headerBuf[8] = (dataLen & 0xFF000000) >> 24;
        headerBuf[9] = (dataLen & 0x00FF0000) >> 16;
        headerBuf[10] = (dataLen & 0x0000FF00) >> 8;
        headerBuf[11] = dataLen & 0x000000FF;
        headerBuf[12] = 0 >> 8;
        headerBuf[13] = 0 & 0xFF;
        headerBuf[14] = 0 >> 8;
        headerBuf[15] = 0 & 0xFF;
    }

    /**
     * 
     * @param {object} headerBuf Uint8Array
     * @param {string} rspValue rsp类型，值为protocalConfig配置的key
     * @param {number} dataLen 数据总长度
     * @param {string} mediaType 媒体类型
     */
    makeCommonHeaderBuf(headerBuf, rspValue, dataLen, mediaType) {
        let currentMsgHeader = null;
        if (mediaType === 'CAMERA') {
            currentMsgHeader = this.cameraMsgHeader;
        }
        if (mediaType === 'MICROPHONE') {
            currentMsgHeader = this.microPhoneMsgHeader;
        }

        if (!currentMsgHeader) {
            return;
        }

        headerBuf[0] = currentMsgHeader.version >> 8;
        headerBuf[1] = currentMsgHeader.version & 0xFF;
        headerBuf[2] = rspValue >> 8;
        headerBuf[3] = rspValue & 0x00FF;
        headerBuf[4] = currentMsgHeader.devType >> 8;
        headerBuf[5] = currentMsgHeader.devType & 0xFF;
        headerBuf[6] = currentMsgHeader.devId >> 8;
        headerBuf[7] = currentMsgHeader.devId & 0xFF;
        headerBuf[8] = (dataLen & 0xFF000000) >> 24;
        headerBuf[9] = (dataLen & 0x00FF0000) >> 16;
        headerBuf[10] = (dataLen & 0x0000FF00) >> 8;
        headerBuf[11] = dataLen & 0x000000FF;
        headerBuf[12] = currentMsgHeader.nextProto >> 8;
        headerBuf[13] = currentMsgHeader.nextProto & 0xFF;
        headerBuf[14] = currentMsgHeader.hopLimits >> 8;
        headerBuf[15] = currentMsgHeader.hopLimits & 0xFF;
    }

    /**
     * 
     * @param {object} buf 8位无符号整型数组Uint8Array
     * @returns 消息头信息，除len(unit32)占4位，其他均占两位(unit16)
     */
    getMsgHeader(buf) {
        const strStartIdx = {
            version: 0,
            optType: 2,
            devType: 4,
            devId: 6,
            len: 8,
            nextProto: 12,
            hopLimits: 14
        };
        let headerObj = {
            version: 0,
            optType: 0,
            devType: 0,
            devId: 0,
            len: 0,
            nextProto: 0,
            hopLimits: 0
        };
        for(let keyName in headerObj) {
            if (keyName === 'len') {
                headerObj[keyName] = this.util.getUnit32FromArray(buf, strStartIdx[keyName]);
            } else {
                headerObj[keyName] = this.util.getUnit16FromArray(buf, strStartIdx[keyName]);
            }
        }

        return headerObj;
    }

    /**
     * 
     * @param {object} bodyBuf 8位无符号整型数组Uint8Array 媒体消息类的body体
     * @param {string} bodyType 消息类型：CAMERA、MICROPHONE
     * @returns 
     */
    getMsgBody(bodyBuf, bodyType) {
        let startIdx = {};
        let bodyObj = {};
        if (bodyType === 'CAMERA') {
            startIdx.width = 0;
            startIdx.height = 2;
            startIdx.fps = 4;

            bodyObj.width = 720;
            bodyObj.height = 1280;
            bodyObj.fps = 30;
        }
        if (bodyType === 'MICROPHONE') {
            startIdx.maxFrameSize = 0;
            startIdx.foramt = 2;
            startIdx.channel = 6;

            bodyObj.maxFrameSize = 7680;
            bodyObj.foramt = 1;
            bodyObj.channel = 1;
        }

        for(let keyName in bodyObj) {
            bodyObj[keyName] = this.util.getUnit16FromArray(bodyBuf, startIdx[keyName]);
        }

        return bodyObj;
    }
}

export default DeviceHardwareHandler;